using Moq;
using NUnit.Framework;
using Tycho.Grammar;
using Tycho.Parser.Peg;

namespace Tycho.UnitTests.Grammar {
    [TestFixture]
    public class TermMacroBuilderCompilerSpecification {
        [Test]
        public void ShouldCompileListTermMacroBuilder () {
            var builderFactory = new Mock<ITermMacroBuilderFactory> ();
            var compiler = new TermMacroBuilderCompiler(builderFactory.Object);

            var value1Builder = new Mock<ITermMacroBuilder> ().Object;
            builderFactory.Setup (f => f.BuildConstantTermMacroBuilder (It.Is<ITerm> (t => t.IsIdentifierTerm ("value1")))).Returns (value1Builder);
            var value2Builder = new Mock<ITermMacroBuilder> ().Object;
            builderFactory.Setup (f => f.BuildConstantTermMacroBuilder (It.Is<ITerm> (t => t.IsIdentifierTerm ("value2")))).Returns (value2Builder);
            var listBuilder = new Mock<ITermMacroBuilder> ().Object;
            builderFactory.Setup (f => f.BuildListTermMacroBuilder (new [] {value1Builder, value2Builder}, It.IsAny<SourceInformation> ())).Returns (listBuilder);

            ITermMacroBuilder builder = compiler.Compile (TermParser.Parse ("list-term {terms: [value1, value2]}"));
            Assert.That (builder, Is.SameAs (listBuilder));
        }

        [Test]
        public void ShouldCompileCompositeTermMacroBuilder () {
            var builderFactory = new Mock<ITermMacroBuilderFactory> ();
            var compiler = new TermMacroBuilderCompiler(builderFactory.Object);

            var value1Builder = new Mock<ITermMacroBuilder> ().Object;
            builderFactory.Setup (f => f.BuildConstantTermMacroBuilder (It.Is<ITerm> (t => t.IsIdentifierTerm ("value1")))).Returns (value1Builder);
            var value2Builder = new Mock<ITermMacroBuilder> ().Object;
            builderFactory.Setup (f => f.BuildConstantTermMacroBuilder (It.Is<ITerm> (t => t.IsIdentifierTerm ("value2")))).Returns (value2Builder);
            var compositeTermBuilder = new Mock<ITermMacroBuilder> ().Object;
            var subTerms = new [] {new CompositeTermSubTerm("term1", value1Builder), new CompositeTermSubTerm("term2", value2Builder)};
            builderFactory.Setup (f => f.BuildCompositeTermMacroBuilder ("name", subTerms, It.IsAny<SourceInformation> ())).Returns (compositeTermBuilder);

            ITermMacroBuilder builder = compiler.Compile (TermParser.Parse ("composite-term {name: name, sub-terms: [sub-term {name: term1, term: value1}, sub-term {name: term2, term: value2}]}"));
            Assert.That (builder, Is.SameAs (compositeTermBuilder));
        }

        [Test]
        public void ShouldCompileNamedTermMacroBuilder () {
            var builderFactory = new Mock<ITermMacroBuilderFactory> ();
            var compiler = new TermMacroBuilderCompiler(builderFactory.Object);

            var value1Builder = new Mock<ITermMacroBuilder> ().Object;
            builderFactory.Setup (f => f.BuildNamedTermMacroBuilder ("value1")).Returns (value1Builder);

            ITermMacroBuilder builder = compiler.Compile (TermParser.Parse ("expression {name: value1}"));
            Assert.That (builder, Is.SameAs (value1Builder));
        }

        [Test]
        public void ShouldThrowExceptionIfTermNotRecognised () {
            var builderFactory = new Mock<ITermMacroBuilderFactory> ();
            var compiler = new TermMacroBuilderCompiler(builderFactory.Object);

            var value1Builder = new Mock<ITermMacroBuilder> ().Object;
            builderFactory.Setup (f => f.BuildNamedTermMacroBuilder ("value1")).Returns (value1Builder);

            var term = TermParser.Parse ("not-recognised {}");
            Assert.That (() => { compiler.Compile (term); }, Throws.InstanceOf (typeof (TermMacroBuilderCompilerException)));
        }

        [Test]
        public void ShouldCompileIdentifiersToBeIdentifiers () {
            var builderFactory = new Mock<ITermMacroBuilderFactory> ();
            var compiler = new TermMacroBuilderCompiler(builderFactory.Object);

            var value1Builder = new Mock<ITermMacroBuilder> ().Object;
            builderFactory.Setup (f => f.BuildConstantTermMacroBuilder (It.Is<ITerm> (t => t.IsIdentifierTerm ("value1")))).Returns (value1Builder);

            ITermMacroBuilder builder = compiler.Compile (TermParser.Parse ("value1"));
            Assert.That (builder, Is.SameAs (value1Builder));
        }
    }
}